package com.seleniti.enpareja.controller.control.controller;

import org.ioc.commons.flowcontrol.activitymapper.IsActivityManager;
import org.ioc.commons.flowcontrol.activitymapper.ProvidesActivityManager;
import org.ioc.commons.flowcontrol.common.Provider;
import org.ioc.commons.flowcontrol.common.Receiver;
import org.ioc.commons.flowcontrol.common.impl.ReceiverAdapter;
import org.ioc.commons.flowcontrol.eventbus.EventBus;
import org.ioc.commons.flowcontrol.eventbus.IsEventBus;
import org.ioc.commons.flowcontrol.logincontroller.LoginController;
import com.seleniti.enpareja.controller.control.controller.LoginFailedCause;
import org.ioc.commons.flowcontrol.messageboxfactory.MessageBoxFactory;
import org.ioc.commons.flowcontrol.operationmanager.OperationManager;
import org.ioc.commons.flowcontrol.operationmanager.OperationManagerBinder;
import org.ioc.commons.flowcontrol.placecontroller.PlaceController;
import org.ioc.commons.model.globaldata.GlobalDataController;
import org.ioc.commons.model.globaldata.GlobalDataController.Consumer;
import org.ioc.commons.model.globaldata.IsGlobalDataConsumer;
import org.ioc.commons.ui.HasOneWidget;
import org.ioc.commons.utils.FormatterLogger;

import com.seleniti.enpareja.controller.common.GlobalData;
import com.seleniti.enpareja.controller.common.domain.LoggedUserInfo;
import com.seleniti.enpareja.controller.control.factory.ClientFactory;
import com.seleniti.enpareja.controller.control.factory.EnParejaAppController;
import com.seleniti.enpareja.controller.control.place.EnParejaPlace;
import com.seleniti.enpareja.controller.model.integration.service.EnParejaServiceManager;
import com.seleniti.enpareja.controller.ui.main.AppShell;

public abstract class EnParejaAppControllerImpl implements EnParejaAppController, IsGlobalDataConsumer<GlobalData>, ProvidesActivityManager  {

	private static final FormatterLogger logger = FormatterLogger.getLogger(EnParejaAppControllerImpl.class);

	protected final ClientFactory clientFactory;

	protected AppShell shell;
	protected PlaceController placeController;
	protected IsEventBus globalEventBus;
	protected GlobalDataController<GlobalData> globalDataController;
	protected EnParejaServiceManager serviceManager;

	protected OperationManager<Operation> operationManager;
	protected EventBus<Event> eventBus;

	protected HasOneWidget parentView;
	protected Parameters parameters;

	private boolean running;

	private LoginController<LoggedUserInfo, LoginFailedCause, LoginParams, String> loginController;

	private MessageBoxFactory messageBoxFactory;

	public EnParejaAppControllerImpl(ClientFactory clientFactory, OperationManager<Operation> operationManager,
			EventBus<Event> eventBus) {
		this.clientFactory = clientFactory;
		this.operationManager = operationManager;
		this.eventBus = eventBus;
	}

	public EnParejaAppControllerImpl(ClientFactory clientFactory) {
		this.clientFactory = clientFactory;
	}

	private void init() {
		this.shell = this.clientFactory.getShell();
		this.placeController = this.clientFactory.getPlaceController().forThisObject(this);
		this.globalEventBus = this.clientFactory.getGlobalEventBus();
		this.globalDataController = this.clientFactory.getGlobalDataController();
		this.loginController = this.clientFactory.getLoginController();
		this.serviceManager = this.clientFactory.getServiceManager();
		this.messageBoxFactory = this.clientFactory.getMessageBoxFactory().forThisObject(this);
	}

	@Override
	public Parameters getParameters() {
		return this.parameters;
	}

	@Override
	@Performs(Operation.LoadingInitialData)
	@Fires(Event.ApplicationReady)
	public void run(HasOneWidget parentView, Object... params) {

		init();

		this.parameters = (Parameters) (params != null && params.length >= 1 && (params[0] instanceof Parameters) ? params[0]
				: null);

		/**
		 * Setting the Activity mapper
		 */
		IsActivityManager activityManager = getActivityManager();

		/*
		 * We set the main container of the shell as location where all the
		 * views will be shown
		 */
		if (this.shell != null) {
			activityManager.setDisplay(this.shell.mainContainer());
		}

		/**
		 * Registering the providers for the global data
		 */
		initGlobalDataProviders();

		/**
		 * Registering all the necessary global data before continuing loading,
		 * like whether the application is running online or offline, etc.
		 */
		Consumer<GlobalData> consumerRegistration = this.globalDataController.registerGlobalDataConsumer(this);
		if (!this.running) {
			consumerRegistration.requires(GlobalData.IsOnline);
		}
		consumerRegistration.ensureRequirements();

		/**
		 * The application flow will continue on onRequiredGlobalDataReady()
		 * method when the required global data is ready.
		 */

		this.parentView = parentView;

	}

	@Override
	public void onRequiredGlobalDataReady() {
		logger.entering("onRequiredGlobalDataReady");
		/**
		 * Embbed the shell in a parent view if there is one
		 */
		if (parentView != null) {
			parentView.setSingleWidget(this.shell);
		}

		boolean isRunningOnline = (Boolean) this.globalDataController.getGlobalDataValue(GlobalData.IsOnline);

		logger.info("The app is running {0}", isRunningOnline ? "online" : "offline");

		/**
		 * Initializing the main place.
		 */
		EnParejaPlace defaultPlace = EnParejaPlace.Main;
		placeController.initPlaceController(!this.running, defaultPlace);

		/**
		 * Initializing sign in and sign up places
		 */
		loginController.initPlaces(placeController, EnParejaPlace.LoginPlace);

		/**
		 * It indicates the application has loaded all required data
		 */
		if (!this.running) {
			eventBus.fireEvent(Event.ApplicationReady);
			this.running = true;
		}

		logger.exiting("onRequiredGlobalDataReady");
	}

	protected void initGlobalDataProviders() {
		logger.info("Initializing global data providers...");
		this.globalDataController.setGlobalPendingOperationsIndicator(shell.globalLoader());

		this.globalDataController.clearRegisteredGlobalDataProvider();

		/**
		 * Registering the checking for determining if the application is
		 * running online or offline.
		 */
		this.globalDataController.registerGlobalDataProvider(GlobalData.IsOnline, new Provider<Boolean>() {

			@Override
			public void provide(final Receiver<Boolean> callback) {
				Object globalProviderCaller = getGlobalProviderCaller();
				serviceManager.serviceFactory().securityManagementService().isApplicationRunningOnline()
						.to(new ReceiverAdapter<Boolean>() {

							@Override
							public void success(Boolean result) {
								callback.success(result);
							}

							@Override
							public void failure(Throwable e) {
								callback.failure(e);
							}
						}).caller(globalProviderCaller);
			}
		});

		this.loginController.registerLoggedUserInfoProvider(new Provider<LoggedUserInfo>() {

			@Override
			public void provide(Receiver<LoggedUserInfo> callback) {
				Object globalProviderCaller = getGlobalProviderCaller();
				serviceManager.serviceFactory().securityManagementService().getLoggedUserInfo().to(callback)
						.performing(Operation.LoadingLoggedUserInfo, operationManager).caller(globalProviderCaller);
			}
		});
		logger.info("... initialized OK");
	}

	@Override
	public void onRequiredGlobalDataUpdated(GlobalData globalDataUpdated) {
		// Nothing to do here
	}

	@Override
	public void onRequiredGlobalDataNotReadyYet() {
		// Nothing to do here
	}

	@Override
	public EventBus<Event> eventBus() {
		return eventBus;
	}

	@Override
	public OperationManagerBinder<Operation> operationManager() {
		return operationManager;
	}

	protected abstract Object getGlobalProviderCaller();

}
